package com.example.ass7.sprite;

import java.awt.Color;

import biuoop.DrawSurface;

import com.example.ass7.GameEnvironment;
import com.example.ass7.collision.CollisionInfo;
import com.example.ass7.geometryprimitives.Line;
import com.example.ass7.geometryprimitives.Point;
import com.example.ass7.geometryprimitives.Rectangle;
import com.example.ass7.geometryprimitives.Velocity;
import com.example.ass7.level.GameLevel;

/**
 * @author Meirav Ben Izhak
 */
public class Ball implements Sprite {
    // members
    private Point center;
    private final double radius;
    private final java.awt.Color color;
    private Velocity velocity;
    private boolean alienOwn = false;
    // private Point nextCollisionPoint;
    private GameEnvironment gameEnvir;
    private final double speed;

    // borders:
    private final double lowestXPossible;
    private final double lowestYPossible;
    // highest points are calculated according to the board's sizes as
    // given in the instructions
    private final double highestXPossible;
    private final double highestYPossible;

    /**
     * @param center
     *            center
     * @param radius
     *            radius
     * @param velocity
     *            velocity
     * @param color
     *            color
     */
    public Ball(
        Point center, double radius, Velocity velocity, Color color) {
        this.center = center;
        this.velocity = velocity;
        this.radius = radius;
        this.color = color;
        this.gameEnvir = new GameEnvironment();

        this.lowestXPossible = 30; // + this.getSize();
        this.lowestYPossible = 30; // + this.getSize();
        // highest points are calculated according to the board's sizes as
        // given in the instructions
        this.highestXPossible = 770 - this.getSize();
        this.highestYPossible = 650 - this.getSize();

        this.speed = 100;
    }

    /**
     * @return boolean
     */
    public boolean alienOwn() {
        return alienOwn;
    }

    /**
     * @param alienOwn1 alienOwn1
     */
    public void setAlienOwn(boolean alienOwn1) {
        this.alienOwn = alienOwn1;
    }

    /**
     * @param pCenter
     *            center
     * @param pVelocity
     *            velocity
     */
    public void setPosition(Point pCenter,
            Velocity pVelocity) {
        this.center = pCenter;
        this.velocity = pVelocity;
    }

    // accessors
    /**
     * @return x
     */
    public double getX() {
        return this.center.getX();
    }

    /**
     * @return y
     */
    public double getY() {
        return this.center.getY();
    }

    /**
     * @return size.
     */
    public double getSize() {
        return this.radius;
    }

    /**
     * @return color1 nickname for color that won't upset the checkstyle,
     */
    public java.awt.Color getColor() {
        Color color1 = this.color;
        return color1;
    }

    /**
     * @param xToSet
     *            the value to set,
     */
    public void setX(double xToSet) {
        this.center.setX(xToSet);
    }

    /**
     * @param yToSet
     *            the value to set,
     */
    public void setY(double yToSet) {
        this.center.setY(yToSet);
    }

    /**
     * @param game
     *            game
     */
    public void removeFromGame(GameLevel game) {
        game.removeSprite(this);
        // game.removeCollidable(this);
    }

    /**
     * @param toSet
     *            toSet
     */
    public void setGameEnvironment(GameEnvironment toSet) {
        this.gameEnvir = toSet;
    }

    /**
     * @param surface
     *            to draw the ball on
     */
    @Override
    public void drawOn(DrawSurface surface) {
        double x = this.getX();
        double y = this.getY();
        double r = this.getSize();
        surface.setColor(color);
        surface.fillCircle((int) x, (int) y, (int) r);
        surface.setColor(Color.BLACK);
        surface.drawCircle((int) x, (int) y, (int) r);
    }

    /**
     * @param v
     *            value to set.
     */
    public void setVelocity(Velocity v) {
        this.velocity = v;
    }

    /**
     * @param dx
     *            x change value.
     * @param dy
     *            y change value.
     */
    public void setVelocity(double dx,
            double dy) {
        Velocity vel = new Velocity(dx, dy);
        this.velocity = vel;
    }

    /**
     * @return velToReturn the velocity.
     */
    public Velocity getVelocity() {
        Velocity velToReturn = this.velocity;
        return velToReturn;
    }

    /**
     * @param dt
     *            dt
     * @return Line
     */
    public Line calculateTrajectory(double dt) {
        Point start = this.center;
        double endX = this.getX()
                + (this.velocity.getDx() * dt);
        double endY = this.getY()
                + (this.velocity.getDy() * dt);
        Point end = new Point(endX, endY);
        Line trajectory = new Line(start, end);
        return trajectory;
    }

    /**
     * @param dt
     *            dt
     */
    public void moveOneStep(double dt) {

        // 1)computing the trajectory:
        Line trajectory = this.calculateTrajectory(dt);
        // 2) checking if there will be a collision:
        boolean collision = this.gameEnvir.willThereBeACollision(trajectory);
        if (collision) {
            Point nextHit = this.gameEnvir.getClosestCollision(trajectory)
                    .getCollisionPoint();
            CollisionInfo nextCollide = this.gameEnvir
                    .getClosestCollision(trajectory);

            if (nextCollide.getCollisionObject() instanceof Block) {
                Block b = (Block) nextCollide.getCollisionObject();
                if (this.alienOwn() && b.isAlien()) {
                    this.center = this.getVelocity().applyToPoint(this.center,
                            this.speed, dt);
                    return;
                }
            }

            System.out.println("collision Occured");
            System.out.println(String.format("curCenter: %s %s",
                    this.center.getX(), this.center.getY()));
            System.out.println(String.format("velobefore: %s %s", this
                    .getVelocity().getDx(), this.getVelocity().getDy()));
            this.setVelocity(nextCollide.getCollisionObject().hit(this,
                    nextHit, this.getVelocity()));
            Rectangle re = nextCollide.getCollisionObject()
                    .getCollisionRectangle();
            System.out.println(String.format("rec: %s %s %s %s",
                    re.getxLowest(), re.getxHighest(), re.getyLowest(),
                    re.getyHighest()));
            System.out.println(String.format("veloafter: %s %s", this
                    .getVelocity().getDx(), this.getVelocity().getDy()));
            System.out.println(String.format("posHit: %s %s", nextHit.getX(),
                    nextHit.getY()));
            System.out.println("");

            if (nextCollide.getCollisionObject() instanceof Paddle) {
                this.center = this.getVelocity().applyToPoint(this.center,
                        this.speed, dt);
            }

        } else if (this.hitsLeftBorder(trajectory)
                || this.hitsRightBorder(trajectory)) {
            this.setVelocity(-this.velocity.getDx(), this.velocity.getDy());

        } else if (this.hitsLowerBorder(trajectory)
                || this.hitsUpperBorder(trajectory)) {
            this.setVelocity(this.velocity.getDx(), -this.velocity.getDy());
        }

        trajectory = this.calculateTrajectory(dt);
        if (!this.gameEnvir.willThereBeACollision(trajectory)) {
            this.center = this.getVelocity().applyToPoint(this.center,
                    this.speed, dt);
        }
    }

    /**
     * @param trajectory
     *            trajectory
     * @return boolean
     */
    public boolean hitsUpperBorder(Line trajectory) {
        return trajectory.end().getY() > this.highestYPossible;
    }

    /**
     * @param trajectory
     *            trajectory
     * @return boolean
     */
    public boolean hitsLowerBorder(Line trajectory) {
        return trajectory.end().getY() < this.lowestYPossible;
    }

    /**
     * @param trajectory
     *            trajectory
     * @return boolean
     */
    public boolean hitsLeftBorder(Line trajectory) {
        return trajectory.end().getX() < this.lowestXPossible;
    }

    /**
     * @param trajectory
     *            trajectory
     * @return boolean
     */
    public boolean hitsRightBorder(Line trajectory) {
        return trajectory.end().getX() > this.highestXPossible;
    }

    /**
     * @param whichRib
     *            whichRib
     * @param nextHit
     *            nextHit
     * @return Point
     */
    public Point returnNextPoint(String whichRib,
            Point nextHit) {
        double nextX, nextY;
        Point nextPoint;
        if (whichRib.equalsIgnoreCase("UP")) {
            // moves the ball to the next Location:
            nextX = nextHit.getX();
            nextY = nextHit.getY()
                    + (this.radius);
            nextPoint = new Point(nextX, nextY);
            // updates its velocity:
        } else if (whichRib.equalsIgnoreCase("RIGHT")) {
            nextX = nextHit.getX()
                    + (this.radius);
            nextY = nextHit.getY();
            nextPoint = new Point(nextX, nextY);
        } else if (whichRib.equalsIgnoreCase("DOWN")) {
            nextX = nextHit.getX();
            nextY = nextHit.getY()
                    - (this.radius);
            nextPoint = new Point(nextX, nextY);
        } else {
            nextX = nextHit.getX()
                    - (this.radius);
            nextY = nextHit.getY();
            nextPoint = new Point(nextX, nextY);
        }
        return nextPoint;

    }

    @Override
    public void timePassed(double dt) {
        this.moveOneStep(dt);
    }

    /**
     * @param addTo
     *            addTo
     */
    public void addToGame(GameLevel addTo) {
        addTo.addSprite(this);
    }

}
